use axum::Router;
use std::sync::Arc;

use crate::addr::Addr;
use crate::context::AppContext;
use crate::json_helper::JsonHelper;
use crate::log;
use crate::static_files;

pub type ApiCall = fn(ctx: Arc<AppContext>, is_https: bool) -> Router;

pub async fn start(api: ApiCall, ver: &str) -> Result<(), Box<dyn std::error::Error>> {
  let context = AppContext::new().await;
  let log_file_name = context.config["config"]["log_file_name"].str("");
  log::init_logger(log_file_name)?;

  let ctx = context.clone();
  let version = String::from(ver);
  let http = tokio::spawn(serve(ctx, false, api, version));
  let ctx = context.clone();
  let version = String::from(ver);
  let https = tokio::spawn(serve(ctx, true, api, version));
  let (_, _) = tokio::join!(http, https);

  Ok(())
}

pub async fn serve(context: Arc<AppContext>, is_https: bool, api: ApiCall, version: String) {
  use axum_server::tls_rustls::RustlsConfig;
  use chrono::Local;
  use std::net::SocketAddr;

  fn init_api(api: ApiCall, ctx: Arc<AppContext>, is_https: bool) -> Router {
    let config = ctx.config["config"].clone();
    let static_path = config["static_path"].str("public");
    let cache_age_in_minute: i32 = config["static_cache_age_in_minute"].i64(30) as i32;
    let api = api(ctx, is_https);
    Router::new()
      .nest_service("/api", api)
      .fallback_service(static_files::make_service(static_path, cache_age_in_minute))
  }

  let config = &context.config["config"];
  let server_name = config["server_name"].string("W3");
  let protocol = if is_https { "HTTPS" } else { "HTTP" };
  let addr = Addr::new(config, is_https);
  let (is_active, skt_addr) = addr.get();

  let ctx = context.clone();
  let api = init_api(api, ctx, is_https);
  let make_service = api.into_make_service_with_connect_info::<SocketAddr>();

  if is_active {
    let now = &Local::now().to_string()[0..19];
    println!(
      "{} {} server version {} started at {} listening on {}",
      server_name, protocol, version, now, &addr
    );
    let server = if is_https {
      let config = RustlsConfig::from_pem_file("server.cer", "server.key")
        .await
        .unwrap();
      axum_server::bind_rustls(skt_addr, config)
        .serve(make_service)
        .await
    } else {
      axum_server::bind(skt_addr).serve(make_service).await
    };
    server.unwrap();
  } else {
    println!("{} {} server version {} is not active !", server_name, protocol, version);
  }
}
